//
//  GitClient.swift
//  TFSClient
//
//  Created by Samuel Richard Magny on 2015-09-07.
//  Copyright (c) 2015 Samuel Richard Magny. All rights reserved.
//

import Cocoa
import ObjectiveGit
import SwiftTools

class GitClient: ClientAdapter {

    
    //Get/Set
    //-----------------------------------
    override var workingDirectory: String! {
        didSet(newValue)
        {
            initGitRepository(urlFromWorkingDirectory(self.workingDirectory!))
        }
    }
    
    internal var isWorkingDirectoryClean:Bool {
        get{
            if let repo = repository
            {
                return repo.workingDirectoryClean
            }
            
            return true
        }
    }
    
    
    //Privates
    //-----------------------------------
    private var repository:GTRepository?
    private var head:GTReference?
    private var index:GTIndex?
    
    //Fu****g swift converts
    private enum FileRetreiveSortFlag: Int {
        case IncludeUntracked = 0
        case IncludeUnModified = 1
        case ExcludeSubModules = 2
        case RecursiveUntrackedDirectories = 4
        case DisablePathSecMatch = 8
        case RecursiveIgnoredDirectories = 16
        case RenamesHeadToIndex = 32
        case RenamesIndexToWorkingDirectory = 64
        case SortCaseSensitively = 128
        case SortCaseInsensitively = 256
        case RenamesFromReWrites = 512
        case NoRefresh = 1024
        case UpdateIndex = 2048
        case IncludeUnreadable = 4096
        case IncludeUnreadableAsUntracked = 8192
        
        static func BinaryConvert(values:Array<FileRetreiveSortFlag>)->Int{
            var flag:Int = 0
            for sortFlag:FileRetreiveSortFlag in values
            {
                flag |= sortFlag.rawValue
            }
            
            return flag
        }
    }
    private let FileRetreiveSortFlagKey = GTRepositoryStatusOptionsFlagsKey as NSString
    
    //Inits
    //-----------------------------------
    internal override init(workingDirectory:String)
    {
        super.init(workingDirectory: workingDirectory)
        initGitRepository(urlFromWorkingDirectory(workingDirectory))
    }
    
    //Public / Internal funcs
    //-----------------------------------
    internal func retreivePendingChangeFiles() -> OrderedDictionary<String, TFSFile>
    {
        return retreiveFilesUnified([FileRetreiveSortFlag.IncludeUntracked, FileRetreiveSortFlag.UpdateIndex])
    }
    
    internal func stageFile(file:TFSFile) -> TFSFile {
        
        do {
            let index = self.index!
            let modFile = stageFileInMemory(file, index: index)
            file.copyProperties(modFile)
            
            try index.write()
        } catch {
            NSLog("Cannot stage file")
        }
        
        return file
    }
    
    internal func unStageFile(file:TFSFile) -> TFSFile {
        
        return file
    }
    
    internal func stageFiles(files:Array<TFSFile>) -> Array<TFSFile> {
        do {
            let index = self.index!
            
            for file:TFSFile in files {
                let memFile = stageFileInMemory(file, index: index)
                file.copyProperties(memFile)
            }

            try index.write()
            
        }catch _ {
            NSLog("Cannot stage files")
        }
        
        return files
    }
    
    internal func unStageFiles(files:Array<TFSFile>) -> Array<TFSFile>
    {
        NSLog("Un-Staging Files")
        
        let index = self.index!
        
        do {

            for file:TFSFile in files
            {
//                try index.removeFile(file.filePath)

//                file.staged = false
            }
            
//            try index.write()
            
        }catch _ {}
        
        return files
    }
    
    internal func getAllIndexedFiles(onlyStagedOnes:Bool = false) -> Array<TFSFile>
    {
        let index = self.index!
        var indexedFiles = Array<TFSFile>()
        
        do {
            try index.refresh()
            
            for rawEntry in index.entries
            {
                let entry = rawEntry as! GTIndexEntry
                
                if(onlyStagedOnes)
                {
                    if(entry.staged)
                    {
                        let tfsFile = TFSFile(entryStatus: entry.status, filePath: entry.path)
                        indexedFiles.append(tfsFile)
                    }
                }
                else {
                    let tfsFile = TFSFile(entryStatus: entry.status, filePath: entry.path)
                    indexedFiles.append(tfsFile)
                }

                logEntry(entry)
            }
            
        } catch let error as NSError {
            NSLog(error.localizedDescription)
        }
        
        NSLog(NSString(format:"Entry count: %d", index.entryCount) as String)
        
        NSLog(index.description)
        
        return indexedFiles
    }
    
    
    
    
    internal func commitStagedFiles(comments:String, author:String = "", email:String = "") -> CommitModel?
    {
        if(comments.isEmpty || author.isEmpty) {
            return nil
        }
        
        let index = self.index!
        let repository = self.repository!

        do {
            
            let parent = try repository.lookUpObjectByRevParse("refs/heads/master");
            
            
            let tree = try index.writeTree()
            let signature:GTSignature = signatureFromAuthor(author, email: "")
            let rawCommit:GTCommit = try repository.createCommitWithTree(tree, message: comments, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: "HEAD")
            
            return CommitModel(reference: rawCommit)
            
            
        }catch let error as NSError {
            NSLog(error.localizedDescription)
            
            return nil
        }

    }
    
    
    //Privates
    //-----------------------------------
    private func initGitRepository(workingDirectoryUrl:NSURL?)
    {
        
        let errorPointer:NSErrorPointer = nil
        if(workingDirectoryUrl == nil || !workingDirectoryUrl!.checkResourceIsReachableAndReturnError(errorPointer))
        {
            return;
        }
        
        let url = workingDirectoryUrl!

        do {
            repository = try GTRepository(URL: url)
        } catch _ {
            repository = nil
            NSLog("Cannot set Git Repository")
            return
        }
        
        do {
            index = try repository!.index()
        } catch _ {
            index = nil
            return
        }
        
        do {
            head = try repository!.headReference()
        } catch _ {
            head = nil
            return
        }
    }
    
    private func urlFromWorkingDirectory(workingDirectoryPath:String)->NSURL?
    {
        let gitExtension = ".git"
        var urlString = workingDirectoryPath
        
        if(!urlString.lowercaseString.hasSuffix(gitExtension)) {
            if(!urlString.lowercaseString.hasSuffix("/")) {
                urlString += "/"
            }
            urlString += gitExtension
        }
        
        return NSURL(fileURLWithPath: urlString);
    }
    
    private func retreiveFilesUnified(options:Array<FileRetreiveSortFlag>?)->OrderedDictionary<String, TFSFile>
    {
        var pendingFiles:OrderedDictionary<String, TFSFile> = OrderedDictionary()
        do {
            if( self.repository == nil){
                return pendingFiles;
            }
            let repo = self.repository!
            
            var optionsDict:[NSObject : AnyObject]? = nil
            if(options != nil)
            {
                let flag:Int = FileRetreiveSortFlag.BinaryConvert(options!)
                optionsDict = [FileRetreiveSortFlagKey : flag] as [NSObject : AnyObject]
            }
            
            try repo.enumerateFileStatusWithOptions(optionsDict, usingBlock:
                { (headToIndex, indexToWorkingDirectory, unsafeShouldEnumeratingStop) -> Void in
                    
                    var pendingChangeFile:TFSFile?
                    if let file:GTStatusDelta = indexToWorkingDirectory
                    {
                        pendingChangeFile = self.StatusDeltaToTFSFile(file)
                        pendingChangeFile!.staged = false
                    }
                    else if let file:GTStatusDelta = headToIndex
                    {
                        pendingChangeFile = self.StatusDeltaToTFSFile(file)
                        pendingChangeFile!.staged = true
                    }
                    
                    if let pendingChangeFile = pendingChangeFile
                    {
                        pendingFiles[pendingChangeFile.filePath] = pendingChangeFile
                    }
                    
            })
        } catch let error as NSError {
            NSLog(error.localizedDescription)
        }
        catch {
            pendingFiles.removeAll()
        }
        
        return pendingFiles
    }
    
    private func stageFileInMemory(file:TFSFile, index:GTIndex) -> TFSFile {
        
        do {
            try index.addFile(file.filePath)
            file.staged = true

        }catch {
            print("Cannot stage the file")
        }
        
        return file
    }
    
    private func unStageFileInMemory(file:TFSFile, index:GTIndex) -> TFSFile {
        
        do {
            try index.addFile(file.filePath)
            file.staged = false
            
        }catch {
            print("Cannot un-stage the file")
        }
        
        return file
    }

    private func convertIndexEntryStatus(status:GTIndexEntryStatus)->String {
        switch (status)
        {
        case GTIndexEntryStatus.Updated:
            return "Updated"
        case GTIndexEntryStatus.Conflicted:
            return "Conflicted"
        case GTIndexEntryStatus.Added:
            return "Added"
        case GTIndexEntryStatus.Removed:
            return "Removed"
        case GTIndexEntryStatus.UpToDate:
            return "UpToDate"
        }
    }
    
    private func logEntry(entry:GTIndexEntry)
    {
        var log:String = entry.path + "\n"
        log += ("\tIsStages: " + ((entry.staged == true) ? "true" : "false") + "\n")
        log += ("\tStatus: " + convertIndexEntryStatus(entry.status) + "\n")
        
        log += "\n"
        NSLog(log)
    }
    
    private func signatureFromAuthor(authorName:String, email:String = "NoEmail")->GTSignature! {
        let signature:GTSignature = GTSignature(name: authorName, email: email, time: NSDate())!
        
        return signature
    }
 
    private func StatusDeltaToTFSFile(statusDelta:GTStatusDelta)->TFSFile
    {
        let status = statusDelta.status
        let newFile = statusDelta.newFile!
        let oldFile = statusDelta.oldFile!
        
        return TFSFile(status: status, filePath: newFile.path, oldFilePath: oldFile.path)
    }
}
